package com.stream.spring.configuration;

import static org.springframework.util.StringUtils.tokenizeToStringArray;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.NullValueInNestedPathException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import com.google.common.collect.Lists;

//spring在初始化配置信息后，自动调用接口 BeanDefinitionRegistryPostProcessor中方法
//实现接口：EnvironmentAware就能获取到系统环境信息   GenericRegisterBatchBeanDefinition
@Configuration
public class RegisterBatchBeanDefinition implements BeanDefinitionRegistryPostProcessor,ApplicationContextAware{
	
	public RegisterBatchBeanDefinition(){
		System.out.println("++++++++ RegisterBatchBeanDefinition");
	}
	
	private Logger logger = LoggerFactory.getLogger(getClass());
    
	private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
	private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
	
	private static final String GENERIC_CLASS_NAME = "registerBean.generic.generic-class-name";
	private static final String GENERIC_CLASS_PROPERTY = "registerBean.generic.generic-class-property";
	
	//@Value此处无法设置属性值，@Value是在注入@Component组件时才注入其对应的属性值
	//@Value("${spring.autowireentitydao.basePackage}")
//	private String basePackage;
	private static final String BASE_PACKAGE = "registerBean.generic.basePackage";

	private static final String EXCLUDE_PACKAGE = "registerBean.generic.excludePackage";
	
	private static final String EXCLUDE_CLASS = "registerBean.generic.excludeClass";
	
	private static final String PROPERTY_VALUE = "registerBean.generic.propertyValue";
	
	private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();	

	private ApplicationContext context;
	
	private Class<?> genericClass;
	private String genericClassProperty;
	

    //此方法比下面两个方法先调用	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		context = applicationContext;
	}
	/**
	 * 先执行：postProcessBeanDefinitionRegistry()方法，
	 * 在执行：postProcessBeanFactory()方法。
	 */
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		registerInit();
		registerBeanDefinitionBatch(registry);
	}
	
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		
	}
	
	//生成 BeanName 的方法
	protected String getBeanName(Class<?> beanClass, Class<?> referenceClass){
		char[] nameChar = referenceClass.getSimpleName().toCharArray();
		//首字母变转换成小写
		nameChar[0] += 32;
		return new StringBuilder().append(nameChar).append("Dao").toString();
	}	
	
	//生成依赖属性的名称
	protected MutablePropertyValues getMutablePropertyValues(Class<?> referenceClass){
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		
//		//首字母变转换成小写
//		char[] nameChar = referenceClass.getSimpleName().toCharArray();
//		nameChar[0] += 32;
//		StringBuilder referenceName =  new StringBuilder().append(nameChar).append("");
//		BeanReference reference = new RuntimeBeanReference(referenceName.toString());
		//属性为参考已经生成的BeanDefinition
//		propertyValues.add(genericClassProperty, reference);
		
		//属性为字符串变量
		propertyValues.add(genericClassProperty, referenceClass.getName());
		
		return propertyValues;
	}
	
	private void registerInit(){
		String genericClassName= context.getEnvironment().getProperty(GENERIC_CLASS_NAME, String.class, "");
		genericClassName = tokenizeToStringArray(genericClassName,
	              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)[0];

	    if (StringUtils.isBlank(genericClassName)){
	    	throw new NullValueInNestedPathException(getClass(), GENERIC_CLASS_NAME);
	    }
		
		String genericClassProperty= context.getEnvironment().getProperty(GENERIC_CLASS_PROPERTY, String.class, "");
		this.genericClassProperty = tokenizeToStringArray(genericClassProperty,
	              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)[0];

	    if (StringUtils.isBlank(this.genericClassProperty)){
	    	throw new NullValueInNestedPathException(getClass(), GENERIC_CLASS_PROPERTY);
	    }
	    
		try {
			genericClass = Class.forName(genericClassName);
		} catch (ClassNotFoundException e) {
//			e.printStackTrace();
			throw new NoSuchBeanDefinitionException(genericClassName);
		}
		
	}
	
	public void registerBeanDefinitionBatch(BeanDefinitionRegistry registry)throws BeansException{
		Class<?>[] referenceClasses = getReferenceClasses();
		for(Class referenceClass:referenceClasses) {
			registerBean(registry, genericClass, referenceClass);
		}
	}
	
	private void registerBean(BeanDefinitionRegistry registry, Class<?> beanClass,
			Class<?> referenceClass) {

		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(beanClass);
		
		beanDefinition.setPropertyValues(getMutablePropertyValues(referenceClass));
		// 设置beanName
		String beanName = getBeanName(beanClass, referenceClass);
		// bean注册的holer类.
		BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
		// 使用bean注册工具类进行注册.
		BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, registry);
		if(logger.isDebugEnabled()){
			logger.debug("registry beanDefinition bean Name '{}' ,Bean Class[{}] ,Reference class [{}]",beanName, beanClass.getName(), referenceClass.getName());
		}
	}
	
	//获得自动装配的entityDao对应的Entity类名
	public Class<?>[] getReferenceClasses() {
		List<Class<?>> entityNames = Lists.newArrayList();
			
			String basePackages = context.getEnvironment().getProperty(BASE_PACKAGE);
		    if (StringUtils.isBlank(basePackages)){
		    	throw new NullValueInNestedPathException(getClass(), BASE_PACKAGE);
		    }
			
		    String[] excludePackageArray = tokenizeToStringArray(
		    		context.getEnvironment().getProperty(EXCLUDE_PACKAGE, String.class, ""),
		    		ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
		    
		    String[] excludeClassArray = tokenizeToStringArray(
		    		context.getEnvironment().getProperty(EXCLUDE_CLASS, String.class, ""),
		            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
		    
//			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
//					+ resolveBasePackage(basePackages) + "/" + this.resourcePattern;			
//			Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
			
			Resource[] resources = getResource(basePackages);
			MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
			for (Resource resource : resources) {
				if (resource.isReadable()) {
					try {
						ClassMetadata classMetadata = metadataReaderFactory.getMetadataReader(resource).getClassMetadata();
						String className = classMetadata.getClassName();
						boolean flag = false;
						//
						for(String excludePackage :excludePackageArray){
							if (className.contains(excludePackage)){
								flag = true;
								break;
							}
						}
						if (flag){
							continue ;
						}
						//
						for(String excludeClass :excludeClassArray){
							if (className.equals(excludeClass))
							{
								flag = true;
								break;
							}
						}
						if (flag){
							continue ;
						}
						
						entityNames.add(Class.forName(className));
						if(logger.isTraceEnabled()){
							logger.trace("create entity class [{}] Dao", className);
						}
					} catch (Throwable ex) {
						throw new BeanDefinitionStoreException("Failed to read candidate Repository class: " + resource,
								ex);
					}
				}
			}
		
		if (entityNames.isEmpty()){
			return new Class<?>[0];			
		}
		//java.util.List.toArray(String[] a) 这里只需传入一个 数组的实例，得到数组的类型，数组长度没有任何作用，
		//我觉得写0比entityNames.size()效率最高
		Class<?>[] modelNames = entityNames.toArray(new Class<?>[0]);
		return modelNames;
	}

	public Resource[] getResource(String basePackages){
//		String basePackages = environment.getProperty(BASE_PACKAGE);
		
		List<Resource> resources = Lists.newArrayList();
	    String[] basePackageArray = tokenizeToStringArray(basePackages,
	              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
		
		String[] packageSearchPathArray = new String[basePackageArray.length];
		for(int i = 0; i < basePackageArray.length; i++){
			packageSearchPathArray[i] = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
			+ resolveBasePackage(basePackageArray[i]) + "/" + this.resourcePattern;			
		}

		try {
			for(String packageSearchPath : packageSearchPathArray){
				Resource[] sectionResources = resourcePatternResolver.getResources(packageSearchPath);
				Collections.addAll(resources,sectionResources);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return resources.toArray(new Resource[0]);
	}
	
	protected String resolveBasePackage(String basePackage) {
		return ClassUtils.convertClassNameToResourcePath(context.getEnvironment().resolveRequiredPlaceholders(basePackage));
	}
	
	protected String[] resolveBasePackage(String[] basePackages) {
		List<String> paths = Lists.newArrayList();
		for(String basePackage: basePackages){
			String convert =ClassUtils.convertClassNameToResourcePath(context.getEnvironment().resolveRequiredPlaceholders(basePackage));	
			paths.add(convert);
		}
		
		return paths.toArray(new String[0]);
	}



}
